このノートブックの実行例はこちら(HTML版)で確認できます
ページ上部のメニューバーにある Kernel メニューをクリックし、プルダウンメニューから [Change Kernel ...] を選び、gssm2023:Python を選択してください。
ノートブック上部の右隅に表示されたカーネル名が gssm2023:Python になっていることを確認してください。
以下のセルを修正せずに実行してください
import warnings
warnings.simplefilter('ignore')
import random
import numpy as np
# 乱数を固定する
seed = 42
random.seed(seed)
np.random.seed(seed)
# ワードクラウドを描画する
def plot_wordcloud(word_str, width=6, height=4):
# 必要ライブラリのインポート
import matplotlib.pyplot as plt
# ノートブック中にプロットするマジックコマンド
%matplotlib inline
# プロットの準備
fig = plt.figure(figsize=(width, height))
ax = fig.add_subplot(1, 1, 1)
# 指定したプロット位置(ax)にワードクラウドを描画する
plot_wordcloud_ax(ax, word_str)
# プロットの仕上げ
plt.axis("off")
plt.tight_layout()
plt.show()
# 指定したプロット位置(ax)にワードクラウドを描画する
def plot_wordcloud_ax(ax, word_str):
# フォントパスを取得する
font_path = !find ${HOME} -name "ipaexg.ttf"
# 必要ライブラリのインポート
import wordcloud
# ワードクラウドを作成する
wc = wordcloud.WordCloud(
background_color='white',
font_path=font_path[0],
max_font_size=100)
# ワードクラウドを描画する
img = wc.generate(word_str)
ax.imshow(img, interpolation='bilinear')
# トピックモデルによるワードクラウドを描画する
def plot_topic_model(lda, feature_names, n_top_words=20, width=10, height=4):
# フォントパスを取得する
font_path = !find ${HOME} -name "ipaexg.ttf"
# 必要ライブラリのインポート
import matplotlib.pyplot as plt
import wordcloud
# ノートブック中にプロットするマジックコマンド
%matplotlib inline
fig = plt.figure(figsize=(width, height))
# トピックごとのループ
for topic_idx, topic in enumerate(lda.components_):
# トピック中で出現確率の高い頻に単語をソートし,
# ワードクラウドに描画するためテキストを生成する
sorted_text = ' '.join([feature_names[i] for i in topic.argsort()[:-n_top_words-1:-1]])
# ワードクラウドを作成する
wc = wordcloud.WordCloud(
background_color='white',
font_path=font_path[0],
max_font_size=100)
# プロット位置(ax)を選ぶ
ax = fig.add_subplot(2, 3, topic_idx + 1)
# ワードクラウドを描画する
img = wc.generate(sorted_text)
ax.imshow(img, interpolation='bilinear')
ax.set_title(f"Topic # {topic_idx+1}:")
# プロットの仕上げ
plt.tight_layout()
plt.show()
# 共起ネットワーク図を描画する (抽出語-抽出語用)
def plot_cooccur_network(df, word_counts, cutoff, width=8, height=8):
# 必要ライブラリのインポート
import matplotlib.pyplot as plt
import japanize_matplotlib
# プロットの準備
plt.figure(figsize=(width, height))
fig = plt.figure(figsize=(width, height))
# プロット位置(ax)を選ぶ
ax = fig.add_subplot(1, 1, 1)
# 指定したプロット位置(ax)に共起ネットワーク図を描画する
plot_cooccur_network_ax(ax, df, word_counts, cutoff)
# プロットの仕上げ
plt.axis("off")
plt.show()
# 指定したプロット位置(ax)に共起ネットワーク図を描画する
def plot_cooccur_network_ax(ax, df, word_counts, cutoff):
# 必要ライブラリのインポート
import numpy as np
import networkx as nx
from networkx.algorithms import community
from networkx.drawing.nx_agraph import graphviz_layout
import matplotlib.pyplot as plt
import japanize_matplotlib
# ノートブック中にプロットするマジックコマンド
%matplotlib inline
# 共起行列の中身(numpy行列)を取り出す
Xc = df.values
# 共起行列中の最大値を求める
Xc_max = Xc.max()
# プロットする単語リストを取得する
words = df.columns
# プロットする単語の出現頻度の最大値を求める (正規化用)
count_max = word_counts.max()
weights_w, weights_c = [], []
# 共起行列の要素ごとのループ (値がゼロの要素はスキップ)
for i, j in zip(*Xc.nonzero()):
# 対角行列でかつ値がしきい値を超えるものを保持する
if i < j and Xc[i,j] > cutoff:
# ノード: 一方の単語とノードの大きさ(正規化した出現頻度)を保持する
weights_w.append((words[i], {'weight': word_counts[i] / count_max}))
# ノード: 他方の単語とノードの大きさ(正規化した出現頻度)を保持する
weights_w.append((words[j], {'weight': word_counts[j] / count_max}))
# エッジ: 両端の単語を結ぶエッジの太さ(正規化した共起行列の値)を保持する
weights_c.append((words[i], words[j], Xc[i,j] / Xc_max))
# グラフの作成
G = nx.Graph()
G.add_nodes_from(weights_w)
G.add_weighted_edges_from(weights_c)
G.remove_nodes_from(list(nx.isolates(G)))
# G = nx.minimum_spanning_tree(G)
# pos = nx.spring_layout(G, k=0.3)
pos = graphviz_layout(G, prog='neato', args='-Goverlap="scalexy" -Gsep="+6" -Gnodesep=0.8 -Gsplines="polyline" -GpackMode="graph" -Gstart={}'.format(43))
# プロット用にノートとエッジの重みをリストに変換する
weights_n = np.array(list(nx.get_node_attributes(G, 'weight').values()))
weights_e = np.array(list(nx.get_edge_attributes(G, 'weight').values()))
# サブグラフの検出
communities = community.greedy_modularity_communities(G)
color_map = []
for node in G:
for i, c in enumerate(communities):
if node in c:
color_map.append(i)
# グラフの描画
nx.draw_networkx_nodes(G, pos, node_color=color_map, alpha=0.7, cmap=plt.cm.Set2, node_size=5000 * weights_n, ax=ax)
nx.draw_networkx_edges(G, pos, edge_color='gray', edge_cmap=plt.cm.Blues, alpha=0.7, width=3 * weights_e, ax=ax)
nx.draw_networkx_labels(G, pos, font_family='IPAexGothic', ax=ax)
# ax.axis('off')
# 指定したプロット位置(ax)に共起ネットワーク図を描画する
def plot_cooccur_network_with_code_ax(ax, df, word_counts, cutoff, coding_rule=None):
# 必要ライブラリのインポート
import numpy as np
import networkx as nx
from networkx.algorithms import community
from networkx.drawing.nx_agraph import graphviz_layout
import matplotlib.pyplot as plt
import japanize_matplotlib
# ノートブック中にプロットするマジックコマンド
%matplotlib inline
# 共起行列の中身(numpy行列)を取り出す
Xc = df.values
# 共起行列中の最大値を求める
Xc_max = Xc.max()
# プロットする単語リストを取得する
words = df.columns
# プロットする単語の出現頻度の最大値を求める (正規化用)
count_max = word_counts.max()
weights_w, weights_c = [], []
# 共起行列の要素ごとのループ (値がゼロの要素はスキップ)
for i, j in zip(*Xc.nonzero()):
# 対角行列でかつ値がしきい値を超えるものを保持する
if i < j and Xc[i,j] > cutoff:
# ノード: 一方の単語とノードの大きさ(正規化した出現頻度)を保持する
weights_w.append((words[i], {'weight': word_counts[i] / count_max}))
# ノード: 他方の単語とノードの大きさ(正規化した出現頻度)を保持する
weights_w.append((words[j], {'weight': word_counts[j] / count_max}))
# エッジ: 両端の単語を結ぶエッジの太さ(正規化した共起行列の値)を保持する
weights_c.append((words[i], words[j], Xc[i,j] / Xc_max))
# グラフの作成
G = nx.Graph()
G.add_nodes_from(weights_w)
G.add_weighted_edges_from(weights_c)
G.remove_nodes_from(list(nx.isolates(G)))
# G = nx.minimum_spanning_tree(G)
# pos = nx.spring_layout(G, k=0.3)
pos = graphviz_layout(G, prog='neato', args='-Goverlap="scalexy" -Gsep="+6" -Gnodesep=0.8 -Gsplines="polyline" -GpackMode="graph" -Gstart={}'.format(43))
# プロット用にノートとエッジの重みをリストに変換する
nodelist_c = [node for node in G.nodes if node in coding_rule]
nodelist_w = [node for node in G.nodes if node not in coding_rule]
weights_c = np.array([G.nodes[node]['weight'] for node in G.nodes if node in coding_rule])
weights_w = np.array([G.nodes[node]['weight'] for node in G.nodes if node not in coding_rule])
weights_e = np.array(list(nx.get_edge_attributes(G, 'weight').values()))
# サブグラフの検出
communities = community.greedy_modularity_communities(G)
color_map_c = []
for node in G:
if node in coding_rule:
for i, c in enumerate(communities):
if node in c:
color_map_c.append(i)
color_map_w = []
for node in G:
if node not in coding_rule:
for i, c in enumerate(communities):
if node in c:
color_map_w.append(i)
# グラフの描画
nx.draw_networkx_nodes(G, pos, node_color=color_map_c, alpha=0.7, cmap=plt.cm.Set2, node_size=5000 * weights_c, ax=ax, nodelist=nodelist_c, node_shape='s', edgecolors='red')
nx.draw_networkx_nodes(G, pos, node_color=color_map_w, alpha=0.7, cmap=plt.cm.Set2, node_size=5000 * weights_w, ax=ax, nodelist=nodelist_w)
nx.draw_networkx_edges(G, pos, edge_color='gray', edge_cmap=plt.cm.Blues, alpha=0.7, width=3 * weights_e, ax=ax)
nx.draw_networkx_labels(G, pos, font_family='IPAexGothic', ax=ax)
# ax.axis('off')
# 共起ネットワークを描画する (外部変数-抽出語用)
def plot_attrs_network(df, attr_counts, word_counts, cutoff, width=8, height=8):
# 必要ライブラリのインポート
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import japanize_matplotlib
from networkx.drawing.nx_agraph import graphviz_layout
# ノートブック中にプロットするマジックコマンド
%matplotlib inline
# 共起行列の中身(numpy行列)を取り出す
Xc = df.values
# 共起行列中の最大値を求める
Xc_max = Xc.max()
# プロットする属性(外部変数等)リストを取得する
attrs = list(df.index)
# プロットする属性(外部変数等)の最大数を求める (正規化用)
attr_count_max = attr_counts.max()
# プロットする単語リストを取得する
words = list(df.columns)
# プロットする単語の出現頻度の最大値を求める (正規化用)
word_count_max = word_counts.max()
weights_n, weights_c = [], []
# 共起行列の要素ごとのループ
for i, j in zip(*Xc.nonzero()):
# 値がしきい値を超えるものを保持する (値がゼロの要素はスキップ)
if Xc[i,j] > cutoff:
# ノード: 属性(外部変数等)とノードの大きさ(正規化した属性数)を保持する
weights_n.append((attrs[i], {'weight': attr_counts[i] / attr_count_max, 'type': 'attr'}))
# ノード: 単語とノードの大きさ(正規化した出現頻度)を保持する
weights_n.append((words[j], {'weight': word_counts[j] / word_count_max, 'type': 'word'}))
# エッジ: 属性(外部変数等)と単語を結ぶエッジの太さ(正規化した共起行列の値)を保持する
weights_c.append((attrs[i], words[j], Xc[i,j] / Xc_max))
# プロットの準備
plt.figure(figsize=(width, height))
# グラフの作成
G = nx.Graph()
G.add_nodes_from(weights_n)
G.add_weighted_edges_from(weights_c)
G.remove_nodes_from(list(nx.isolates(G)))
# G = nx.minimum_spanning_tree(G)
# pos = nx.spring_layout(G, k=0.3)
pos = graphviz_layout(G, prog='neato', args='-Goverlap="scalexy" -Gsep="+6" -Gnodesep=0.8 -Gsplines="polyline" -GpackMode="graph" -Gstart={}'.format(43))
# プロット用にノートとエッジの重みをリストに変換する
nodelist_a = [node for node in G.nodes if G.nodes[node]['type'] == 'attr']
nodelist_w = [node for node in G.nodes if G.nodes[node]['type'] == 'word']
weights_a = np.array([G.nodes[node]['weight'] for node in G.nodes if G.nodes[node]['type'] == 'attr'])
weights_w = np.array([G.nodes[node]['weight'] for node in G.nodes if G.nodes[node]['type'] == 'word'])
weights_e = np.array(list(nx.get_edge_attributes(G, 'weight').values()))
# 属性と単語を色分けする
color_map = []
for node in G:
if G.nodes[node]['type'] == 'word':
color_map.append(G.degree(node)+3) # "+3"はカラーマップをシフトする調整値
# グラフの描画
nx.draw_networkx_nodes(G, pos, node_color='lightsalmon', alpha=0.7, cmap=plt.cm.Set2, node_size=1000 * weights_a, nodelist=nodelist_a, node_shape='s')
nx.draw_networkx_nodes(G, pos, node_color=color_map, alpha=0.7, cmap=plt.cm.Set2, node_size=5000 * weights_w, nodelist=nodelist_w)
nx.draw_networkx_edges(G, pos, edge_color='gray', edge_cmap=plt.cm.Blues, alpha=0.7, width=3 * weights_e)
nx.draw_networkx_labels(G, pos, font_family='IPAexGothic')
# プロットの仕上げ
plt.axis("off")
plt.show()
# 係り受けによる共起ネットワークを描画する
def plot_dependency_network(df, word_counts, cutoff, width=8, height=8):
# 必要ライブラリのインポート
import numpy as np
import networkx as nx
from networkx.algorithms import community
import matplotlib.pyplot as plt
import japanize_matplotlib
from networkx.drawing.nx_agraph import graphviz_layout
# ノートブック中にプロットするマジックコマンド
%matplotlib inline
# 共起行列の中身(numpy行列)を取り出す
Xc = df.values
# 共起行列中の最大値を求める
Xc_max = Xc.max()
# プロットする単語リストを取得する
words = df.columns
# プロットする単語の出現頻度の最大値を求める (正規化用)
count_max = word_counts.max()
weights_w, weights_c = [], []
# 共起行列の要素ごとのループ
for i, j in zip(*Xc.nonzero()):
# 対角行列でかつ値がしきい値を超えるものを保持する (値がゼロの要素はスキップ)
if i != j and Xc[i,j] > cutoff:
# ノード: 一方の単語とノードの大きさ(正規化した出現頻度)を保持する
weights_w.append((words[i], {'weight': word_counts[i] / count_max}))
# ノード: 他方の単語とノードの大きさ(正規化した出現頻度)を保持する
weights_w.append((words[j], {'weight': word_counts[j] / count_max}))
# エッジ: 両端の単語を結ぶエッジの太さ(正規化した共起行列の値)を保持する
weights_c.append((words[i], words[j], Xc[i,j] / Xc_max))
# プロットの準備
plt.figure(figsize=(width, height))
# グラフの作成
G = nx.DiGraph()
G.add_nodes_from(weights_w)
G.add_weighted_edges_from(weights_c)
G.remove_nodes_from(list(nx.isolates(G)))
# G = nx.minimum_spanning_tree(G)
# pos = nx.spring_layout(G, k=0.3)
pos = graphviz_layout(G, prog='neato', args='-Goverlap="scalexy" -Gsep="+6" -Gnodesep=0.8 -Gsplines="polyline" -GpackMode="graph" -Gstart={}'.format(43))
# プロット用にノートとエッジの重みをリストに変換する
weights_n = np.array(list(nx.get_node_attributes(G, 'weight').values()))
weights_e = np.array(list(nx.get_edge_attributes(G, 'weight').values()))
# サブグラフの検出
communities = community.greedy_modularity_communities(G)
color_map = []
for node in G:
for i, c in enumerate(communities):
if node in c:
color_map.append(i)
# グラフの描画
nx.draw_networkx_nodes(G, pos, node_color=color_map, alpha=0.7, cmap=plt.cm.Set2, node_size=5000 * weights_n)
nx.draw_networkx_edges(G, pos, edge_color='gray', edge_cmap=plt.cm.Blues, alpha=0.7, width=3 * weights_e)
nx.draw_networkx_labels(G, pos, font_family='IPAexGothic')
# プロットの仕上げ
plt.axis("off")
plt.show()
# 対応分析の結果をプロットする
def plot_coresp(row_coord, col_coord, row_labels, col_labels, explained_inertia=None, width=8, height=8):
# 必要ライブラリのインポート
import matplotlib.pyplot as plt
import japanize_matplotlib
# ノートブック中にプロットするマジックコマンド
%matplotlib inline
# プロットの準備
# plt.figure(figsize=(width, height))
# 行方向(外部変数)のプロット
plt.plot(row_coord[:, 0], row_coord[:, 1], "*", color='red', alpha=0.5)
for i, label in enumerate(row_labels):
plt.text(row_coord[i, 0], row_coord[i, 1], label, color='red', ha='left', va='bottom')
# 列方向(単語)のプロット
plt.plot(col_coord[:, 0], col_coord[:, 1], "o", color='blue', alpha=0.5)
for i, label in enumerate(col_labels):
plt.text(col_coord[i, 0], col_coord[i, 1], label, color='blue', ha='left', va='bottom')
# 原点を通る水平と垂直線を引く
plt.axvline(0, linestyle='dashed', color='gray', alpha=0.5)
plt.axhline(0, linestyle='dashed', color='gray', alpha=0.5)
# 軸ラベルに寄与率を追記する
if explained_inertia is not None:
plt.xlabel(f"Dim 1 ({explained_inertia[0]:.3f}%)")
plt.ylabel(f"Dim 2 ({explained_inertia[1]:.3f}%)")
# プロットの仕上げ
# plt.axis('equal')
plt.show()
# PCA の結果をプロットする
def plot_pca(coeff, reduced, row_labels, col_labels, var_ratio=None, width=8, height=8):
# 必要ライブラリのインポート
import matplotlib.pyplot as plt
import japanize_matplotlib
# ノートブック中にプロットするマジックコマンド
%matplotlib inline
# プロットの準備
# plt.figure(figsize=(width, height))
# 行方向(外部変数)のプロット
for i, label in enumerate(row_labels):
plt.arrow(0, 0, coeff[i,0], coeff[i,1], color='r', alpha=0.5)
plt.text(coeff[i, 0], coeff[i, 1], label, color='red', ha='left', va='bottom')
# 列方向(単語)のプロット
plt.plot(reduced[:, 0], reduced[:, 1], "o", color='blue', alpha=0.5)
for i, label in enumerate(col_labels):
plt.text(reduced[i, 0], reduced[i, 1], label, color='blue', ha='left', va='bottom')
# 原点を通る水平と垂直線を引く
plt.axvline(0, linestyle='dashed', color='gray', alpha=0.5)
plt.axhline(0, linestyle='dashed', color='gray', alpha=0.5)
# 軸ラベルに寄与率を追記する
if var_ratio is not None:
plt.xlabel(f"Dim 1 ({var_ratio[0]*100:.3f}%)")
plt.ylabel(f"Dim 2 ({var_ratio[1]*100:.3f}%)")
# プロットの仕上げ
# plt.axis('equal')
plt.show()
# 共起頻度行列を Jaccard 係数行列に変換する (抽出語-抽出語用)
def jaccard_coef(cooccur_df, cross_df):
# 必要ライブラリのインポート
import numpy as np
import pandas as pd
# 共起行列の中身(numpy行列)を取り出す
Xc = cooccur_df.values
# Jaccard 係数行列を初期化する (共起行列と同じ形)
Xj = np.zeros(Xc.shape)
# 単語ごとに共起度を集計する
Xc_sum = cross_df.sum(axis=0).values
# 共起行列の要素ごとのループ (値がゼロの要素はスキップ)
for i, j in zip(*Xc.nonzero()):
# 対角行列の要素を取り出す
if i < j:
# Jaccard 係数を求める
Xj[i,j] = Xc[i,j] / (Xc_sum[i] + Xc_sum[j] - Xc[i,j])
# DataFrame 型に整える
jaccard_df = pd.DataFrame(Xj, columns=cooccur_df.columns, index=cooccur_df.columns)
return jaccard_df
# 共起頻度行列を Jaccard 係数行列に変換する (外部変数-抽出語用)
def jaccard_attrs_coef(df, attr_counts, word_counts, total=10000, conditional=False):
# 必要ライブラリのインポート
import numpy as np
import pandas as pd
# 共起行列の中身(numpy行列)を取り出す
Xc = df.values
# Jaccard 係数行列を初期化する (共起行列と同じ形)
Xj = np.zeros(df.shape)
# 共起行列の要素ごとのループ (値がゼロの要素はスキップ)
for i, j in zip(*Xc.nonzero()):
# conditional フラグが True の場合, 条件付き確率 > 前提確率 以外はゼロにする
if not conditional:
# 条件付き確率を求める
conditional_prob = Xc[i,j] / attr_counts[i]
# 前提確率を求める
assumption_prob = word_counts[j] / total
# 条件付き確率 > 前提確率の場合
if conditional_prob > assumption_prob:
# Jaccard 係数を求める
Xj[i,j] = Xc[i,j] / (attr_counts[i] + word_counts[j] - Xc[i,j])
# 条件付き確率 <= 前提確率の場合
else:
# ゼロにする
Xj[i,j] = .0
# conditional フラグが False の場合, すべてのケースで Jaccard 係数を求める (デフォルト)
else:
# Jaccard 係数を求める
Xj[i,j] = Xc[i,j] / (attr_counts[i] + word_counts[j] - Xc[i,j])
# DataFrame 型に整える
jaccard_df = pd.DataFrame(Xj, columns=df.columns, index=df.index)
return jaccard_df
以下のデータがダウンロード済みです
| ファイル名 | 件数 | データセット | 備考 |
|---|---|---|---|
| rakuten-1000-2022-2023.xlsx.zip | 10,000 | •レジャー+ビジネスの 10エリア •エリアごと 1,000件 (ランダムサンプリング) •期間: 2022/1~2023 GW明け |
本講義の全体を通して使用する |
# もし、再度ダウンロードが必要な場合は残りの行のコメントマーク「#」を除去して、このセルを再実行してください
# FILE_ID = "1n-uvGoH7XQhxexN57hYXuFrkGeHKp-HV"
# !gdown --id {FILE_ID}
# !unzip rakuten-1000-2022-2023.xlsx.zip
import numpy as np
import pandas as pd
all_df = pd.read_excel("rakuten-1000-2022-2023.xlsx")
print(all_df.shape)
display(all_df.head())
(10000, 18)
| カテゴリー | エリア | 施設番号 | 施設名 | コメント | 総合 | サービス | 立地 | 部屋 | 設備・アメニティ | 風呂 | 食事 | 旅行の目的 | 同伴者 | 宿泊年月 | 投稿者 | 年代 | 性別 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | A_レジャー | 01_登別 | 5500 | 登別 石水亭 | お風呂が男女時間交代の屋上風呂に入れなかったのが残念でした。食事はスタッフの皆さんがとても親... | 5 | 5 | 4 | 5 | 5.0 | 4.0 | 5.0 | レジャー | 家族 | 44743 | さとちん4359 | 60代 | 女性 |
| 1 | A_レジャー | 01_登別 | 39175 | 登別温泉 登別グランドホテル | 大浴場のアメニティ、洗い場のクレンジング、ソープ、パック等が充実していて、大変良かったです。... | 4 | 5 | 4 | 3 | 5.0 | 5.0 | 5.0 | レジャー | 家族 | 44835 | まなちゃん5695 | 60代 | 女性 |
| 2 | A_レジャー | 01_登別 | 20547 | 北湯沢温泉郷 湯元 ホロホロ山荘 | 犬プラン素泊まり一名で宿泊しましたが、夜8時頃売店でお土産(その時飲む飲み物など)を買う時に... | 1 | 1 | 2 | 2 | 3.0 | 4.0 | NaN | レジャー | 一人 | 44835 | 投稿者 | na | na |
| 3 | A_レジャー | 01_登別 | 139962 | ザ レイクビュー TOYA 乃の風リゾート | 部屋風呂が最高でした。 | 5 | 5 | 5 | 5 | 5.0 | 5.0 | 5.0 | レジャー | 恋人 | 44986 | 投稿者 | na | na |
| 4 | A_レジャー | 01_登別 | 80732 | 登別カルルス温泉 湯元オロフレ荘 | カルルス温泉郷の静かな立地、そして湯元の素晴らしい泉質、美味しいお料理、飾らない中にも親切な... | 5 | 5 | 5 | 5 | 5.0 | 5.0 | 5.0 | レジャー | 家族 | 44986 | 投稿者 | na | na |
コメント列から単語を抽出する (単語を品詞「名詞」「形容詞」「未知語」で絞り込む)
# 必要ライブラリのインポート
from collections import defaultdict
import MeCab
# mecab の初期化
tagger = MeCab.Tagger("-r ../tools/usr/local/etc/mecabrc --unk-feature 未知語")
# 単語頻度辞書の初期化
word_counts = defaultdict(lambda: 0)
# 抽出語情報リストの初期化
words = []
# 半角->全角変換マクロを定義する
ZEN = "".join(chr(0xff01 + i) for i in range(94))
HAN = "".join(chr(0x21 + i) for i in range(94))
HAN2ZEN = str.maketrans(HAN, ZEN)
# ストップワードを定義する
# stopwords = ['する', 'ある', 'ない', 'いう', 'もの', 'こと', 'よう', 'なる', 'ほう']
stopwords = ["湯畑"]
# データ1行ごとのループ
for index, row in all_df.iterrows():
# 半角->全角変換した後で, mecab で解析する
node = tagger.parseToNode(row["コメント"].translate(HAN2ZEN))
# 形態素ごとのループ
while node:
# 解析結果を要素ごとにバラす
features = node.feature.split(',')
# 品詞1 を取り出す
pos1 = features[0]
# 品詞2 を取り出す
pos2 = features[1] if len(features) > 1 else ""
# 原形 を取り出す
base = features[6] if len(features) > 6 else None
# 原型がストップワードに含まれない単語のみ抽出する
if base not in stopwords:
# 「名詞-一般」
if (pos1 == "名詞" and pos2 == "一般"):
base = base if base is not None else node.surface
postag = "名詞"
key = (base, postag)
# 単語頻度辞書をカウントアップする
word_counts[key] += 1
# 抽出語情報をリストに追加する
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
# 「形容動詞」
elif (pos1 == "名詞" and pos2 == "形容動詞語幹"):
base = base if base is not None else node.surface
base = f"{base}"
postag = "形容動詞"
key = (base, postag)
# 単語頻度辞書をカウントアップする
word_counts[key] += 1
# 抽出語情報をリストに追加する
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
# 「形容詞」
elif pos1 == "形容詞":
base = base if base is not None else node.surface
postag = "形容詞"
key = (base, postag)
# 単語頻度辞書をカウントアップする
word_counts[key] += 1
# 抽出語情報をリストに追加する
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
# 「未知語」
elif pos1 == "未知語":
base = base if base is not None else node.surface
postag = "未知語"
key = (base, postag)
# 単語頻度辞書をカウントアップする
word_counts[key] += 1
# 抽出語情報をリストに追加する
words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])
# 次の形態素へ
node = node.next
# DataFrme 型に整える
columns = [
"文書ID",
# "単語ID",
"表層",
"品詞",
"カテゴリー",
"エリア",
"dict_key",
]
docs_df = pd.DataFrame(words, columns=columns)
# DataFrame を表示する
print(docs_df.shape)
display(docs_df.head())
(149372, 6)
| 文書ID | 表層 | 品詞 | カテゴリー | エリア | dict_key | |
|---|---|---|---|---|---|---|
| 0 | 1 | 風呂 | 名詞 | A_レジャー | 01_登別 | (風呂, 名詞) |
| 1 | 1 | 男女 | 名詞 | A_レジャー | 01_登別 | (男女, 名詞) |
| 2 | 1 | 屋上 | 名詞 | A_レジャー | 01_登別 | (屋上, 名詞) |
| 3 | 1 | 風呂 | 名詞 | A_レジャー | 01_登別 | (風呂, 名詞) |
| 4 | 1 | 残念 | 形容動詞 | A_レジャー | 01_登別 | (残念, 形容動詞) |
抽出語の出現頻度をカウントする
# 「文書-抽出語」 表から単語の出現回数をカウントする
word_list = []
for i, (k, v) in enumerate(sorted(word_counts.items(), key=lambda x:x[1], reverse=True)):
word_list.append((i, k[0], v, k))
# DataFrame 型に整える
columns = [
"単語ID",
"表層",
"出現頻度",
"dict_key"
]
# DataFrame を表示する
word_counts_df = pd.DataFrame(word_list, columns=columns)
print(word_counts_df.shape)
display(word_counts_df.head(10))
(8390, 4)
| 単語ID | 表層 | 出現頻度 | dict_key | |
|---|---|---|---|---|
| 0 | 0 | 部屋 | 6689 | (部屋, 名詞) |
| 1 | 1 | 良い | 5257 | (良い, 形容詞) |
| 2 | 2 | ホテル | 2831 | (ホテル, 名詞) |
| 3 | 3 | 風呂 | 2702 | (風呂, 名詞) |
| 4 | 4 | 美味しい | 2249 | (美味しい, 形容詞) |
| 5 | 5 | ない | 2124 | (ない, 形容詞) |
| 6 | 6 | スタッフ | 1712 | (スタッフ, 名詞) |
| 7 | 7 | 温泉 | 1705 | (温泉, 名詞) |
| 8 | 8 | よい | 1446 | (よい, 形容詞) |
| 9 | 9 | 立地 | 1374 | (立地, 名詞) |
「文書-抽出語」表を作成する (出現回数 Top 1000語)
# 「単語出現回数」 表から出現回数Top 1000語のみ抽出する
word_counts_1000_df = word_counts_df[0:1000]
# 「文書-抽出語」 表も出現回数Top 150語のみに絞り込む
merged_df = pd.merge(docs_df, word_counts_1000_df, how="inner", on="dict_key", suffixes=["", "_right"])
docs_1000_df = merged_df[["文書ID", "単語ID", "表層", "品詞", "カテゴリー", "エリア", "dict_key"]]
# 「カテゴリー,エリア」でクロス集計する
cross_1000_df = pd.crosstab(
[
docs_1000_df['カテゴリー'],
docs_1000_df['エリア'],
docs_1000_df['文書ID']
],
docs_1000_df['単語ID'], margins=False
)
cross_1000_df.columns = word_counts_1000_df["表層"]
「文書-抽出語」表を {0,1} に変換する
# 「文書-抽出語」 表を {0,1} に変換する
cross_1000_df[cross_1000_df > 0] = 1
# DataFrame を表示する
print(cross_1000_df.shape)
display(cross_1000_df)
(9879, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | 美味しい | ない | スタッフ | 温泉 | よい | 立地 | ... | 長湯 | 懐かしい | 人柄 | 人達 | アンケート | トータル | 白 | レンタカー | ダメ | スポット | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| カテゴリー | エリア | 文書ID | |||||||||||||||||||||
| A_レジャー | 01_登別 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 3 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 4 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 5 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| B_ビジネス | 10_福岡 | 9996 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 9997 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 9998 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 9999 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
| 10000 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
9879 rows × 1000 columns
# 「カテゴリー」のクロス集計と「エリア」のクロス集計を連結する
aggregate_df = pd.concat(
[
cross_1000_df.groupby(level='カテゴリー').sum(),
cross_1000_df.groupby(level='エリア').sum()
]
)
# DataFrame を表示する
print(aggregate_df.shape)
display(aggregate_df)
(12, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | 美味しい | ない | スタッフ | 温泉 | よい | 立地 | ... | 長湯 | 懐かしい | 人柄 | 人達 | アンケート | トータル | 白 | レンタカー | ダメ | スポット |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A_レジャー | 2398 | 2046 | 757 | 1535 | 1430 | 880 | 888 | 1188 | 631 | 518 | ... | 18 | 18 | 18 | 13 | 9 | 8 | 13 | 6 | 11 | 13 |
| B_ビジネス | 2170 | 1655 | 1284 | 608 | 532 | 749 | 523 | 110 | 574 | 810 | ... | 1 | 1 | 1 | 6 | 6 | 11 | 4 | 13 | 8 | 6 |
| 01_登別 | 447 | 409 | 194 | 323 | 255 | 187 | 148 | 222 | 114 | 38 | ... | 6 | 4 | 1 | 3 | 3 | 3 | 2 | 1 | 1 | 1 |
| 02_草津 | 488 | 434 | 181 | 352 | 274 | 180 | 154 | 275 | 117 | 155 | ... | 4 | 7 | 8 | 4 | 3 | 2 | 2 | 1 | 2 | 1 |
| 03_箱根 | 548 | 436 | 134 | 326 | 355 | 202 | 212 | 212 | 133 | 57 | ... | 4 | 2 | 2 | 3 | 1 | 1 | 5 | 1 | 5 | 1 |
| 04_道後 | 416 | 349 | 191 | 181 | 174 | 130 | 135 | 225 | 137 | 176 | ... | 3 | 1 | 3 | 1 | 0 | 0 | 1 | 2 | 2 | 2 |
| 05_湯布院 | 499 | 418 | 57 | 353 | 372 | 181 | 239 | 254 | 130 | 92 | ... | 1 | 4 | 4 | 2 | 2 | 2 | 3 | 1 | 1 | 8 |
| 06_札幌 | 452 | 346 | 255 | 121 | 129 | 151 | 114 | 38 | 103 | 166 | ... | 0 | 0 | 1 | 1 | 1 | 2 | 0 | 7 | 4 | 1 |
| 07_名古屋 | 434 | 310 | 241 | 116 | 97 | 144 | 102 | 18 | 133 | 141 | ... | 0 | 0 | 0 | 1 | 2 | 0 | 2 | 1 | 1 | 1 |
| 08_東京 | 441 | 338 | 240 | 106 | 99 | 131 | 99 | 12 | 104 | 166 | ... | 0 | 0 | 0 | 2 | 0 | 1 | 1 | 0 | 0 | 1 |
| 09_大阪 | 431 | 317 | 297 | 135 | 88 | 162 | 93 | 20 | 121 | 161 | ... | 1 | 1 | 0 | 2 | 3 | 5 | 1 | 0 | 1 | 1 |
| 10_福岡 | 412 | 344 | 251 | 130 | 119 | 161 | 115 | 22 | 113 | 176 | ... | 0 | 0 | 0 | 0 | 0 | 3 | 0 | 5 | 2 | 2 |
12 rows × 1000 columns
# 抽出語の出現回数を取得する
word_counts = cross_1000_df.sum(axis=0).values
# 属性(外部変数)出現数を取得する
attr_counts = np.hstack(
[
all_df.value_counts('カテゴリー').values,
all_df.value_counts('エリア').values
]
)
# 共起行列の中身を Jaccard 係数に入れ替える
jaccard_attrs_df = jaccard_attrs_coef(aggregate_df, attr_counts, word_counts, total=10000, conditional=False)
# DataFrame を表示する
print(jaccard_attrs_df.shape)
display(jaccard_attrs_df)
(12, 1000)
| 表層 | 部屋 | 良い | ホテル | 風呂 | 美味しい | ない | スタッフ | 温泉 | よい | 立地 | ... | 長湯 | 懐かしい | 人柄 | 人達 | アンケート | トータル | 白 | レンタカー | ダメ | スポット |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A_レジャー | 0.334449 | 0.307438 | 0.000000 | 0.273716 | 0.258496 | 0.153070 | 0.160782 | 0.232485 | 0.113204 | 0.000000 | ... | 0.003599 | 0.003599 | 0.003599 | 0.002597 | 0.001798 | 0.000000 | 0.002598 | 0.000000 | 0.002196 | 0.002597 |
| B_ビジネス | 0.000000 | 0.000000 | 0.223033 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.146792 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.002196 | 0.000000 | 0.002597 | 0.000000 | 0.000000 |
| 01_登別 | 0.000000 | 0.095294 | 0.000000 | 0.114539 | 0.094200 | 0.076577 | 0.065400 | 0.106936 | 0.000000 | 0.000000 | ... | 0.005923 | 0.003941 | 0.000000 | 0.002953 | 0.002964 | 0.002953 | 0.001970 | 0.000000 | 0.000000 | 0.000000 |
| 02_草津 | 0.096063 | 0.101711 | 0.000000 | 0.126120 | 0.101935 | 0.073499 | 0.068232 | 0.135937 | 0.000000 | 0.071330 | ... | 0.003941 | 0.006917 | 0.007913 | 0.003941 | 0.002964 | 0.001967 | 0.001970 | 0.000000 | 0.001967 | 0.000000 |
| 03_箱根 | 0.109163 | 0.102227 | 0.000000 | 0.115726 | 0.136172 | 0.083230 | 0.096407 | 0.101630 | 0.064189 | 0.000000 | ... | 0.003941 | 0.001967 | 0.001967 | 0.002953 | 0.000000 | 0.000000 | 0.004941 | 0.000000 | 0.004931 | 0.000000 |
| 04_道後 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.108538 | 0.066248 | 0.081784 | ... | 0.002953 | 0.000000 | 0.002953 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.001967 | 0.001967 | 0.001967 |
| 05_湯布院 | 0.098442 | 0.097595 | 0.000000 | 0.126523 | 0.143629 | 0.073938 | 0.110037 | 0.124266 | 0.062651 | 0.000000 | ... | 0.000000 | 0.003941 | 0.003941 | 0.001967 | 0.001974 | 0.001967 | 0.002959 | 0.000000 | 0.000000 | 0.007913 |
| 06_札幌 | 0.000000 | 0.000000 | 0.091529 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.076781 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.001967 | 0.000000 | 0.006917 | 0.003941 | 0.000000 |
| 07_名古屋 | 0.000000 | 0.000000 | 0.086071 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.064189 | 0.064472 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.001974 | 0.000000 | 0.001970 | 0.000000 | 0.000000 | 0.000000 |
| 08_東京 | 0.000000 | 0.000000 | 0.085684 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.076781 | ... | 0.000000 | 0.000000 | 0.000000 | 0.001967 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 09_大阪 | 0.000000 | 0.000000 | 0.108236 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.058061 | 0.074296 | ... | 0.000000 | 0.000000 | 0.000000 | 0.001967 | 0.002964 | 0.004931 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 10_福岡 | 0.000000 | 0.000000 | 0.089964 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.081784 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.002953 | 0.000000 | 0.004931 | 0.001967 | 0.001967 |
12 rows × 1000 columns
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline
# サブルーチン
def sort_and_plot(name, group):
# 「カテゴリー」ごとに Jaccard 係数でソートする
sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]
# Jaccard 係数Top 75語をソートして抽出する
group_cross_df = group.iloc[:,sorted_columns]
# 共起行列を作成する
X = group_cross_df.values
X = csc_matrix(X)
Xc = (X.T * X)
Xc = np.triu(Xc.toarray())
# 共起行列を DataFrame に整える
group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)
# 共起行列の中身を Jaccard 係数に入れ替える
group_jaccard_df = jaccard_coef(group_cooccur_df, group_cross_df)
# 抽出語の出現回数を取得する
word_counts = group_cross_df.sum(axis=0).values
# プロットする
ax = fig.add_subplot(4, 3, i+1)
plot_cooccur_network_ax(ax, group_jaccard_df, word_counts, np.sort(group_jaccard_df.values.reshape(-1))[::-1][120])
ax.set_title(name)
# プロットの準備
fig = plt.figure(figsize=(20, 28))
i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
# サブルーチンを呼ぶ
sort_and_plot(name, group)
i += 1
# エリアごとのループ
for sub_name, sub_group in group.groupby(level='エリア'):
# サブルーチンを呼ぶ
sort_and_plot(sub_name, sub_group)
i += 1
# プロットの仕上げ
plt.tight_layout()
plt.show()
# コーディングルール
coding_pos = ["良い","美味しい","広い","多い","素晴らしい","嬉しい","気持ちよい","楽しい","近い","大きい","気持ち良い","温かい","早い","優しい","新しい","暖かい","快い","明るい","美しい","可愛い","満足"]
coding_neg = ["古い","無い","高い","悪い","小さい","狭い","少ない","寒い","遅い","熱い","欲しい","暑い","冷たい","遠い","臭い","暗い","うるさい","ない","無い","残念","改善","不満"]
# DataFrame を初期化する
cross_1000_ps_df = cross_1000_df.copy()
cross_1000_ps_df['ポジ'] = 0
cross_1000_ps_df['ネガ'] = 0
cross_1000_ps_df['総合1-2'] = 0
cross_1000_ps_df['総合4-5'] = 0
# コーディングルールを適用する (ポジ・ネガ)
pos_index = docs_df['表層'].str.contains("|".join(coding_pos))
neg_index = docs_df['表層'].str.contains("|".join(coding_neg))
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(docs_df.loc[pos_index, '文書ID']), 'ポジ'] = 1
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(docs_df.loc[neg_index, '文書ID']), 'ネガ'] = 1
# コーディングルールを適用する (総合評価)
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(all_df[all_df['総合'] <=2].index), '総合1-2'] = 1
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(all_df[all_df['総合'] >=4].index), '総合4-5'] = 1
cross_1000_ps_df = cross_1000_ps_df[['ポジ','ネガ','総合1-2','総合4-5']]
# DataFrame を表示する
print(cross_1000_ps_df.shape)
display(cross_1000_ps_df)
(9879, 4)
| 表層 | ポジ | ネガ | 総合1-2 | 総合4-5 | ||
|---|---|---|---|---|---|---|
| カテゴリー | エリア | 文書ID | ||||
| A_レジャー | 01_登別 | 1 | 1 | 1 | 0 | 1 |
| 2 | 1 | 0 | 1 | 0 | ||
| 3 | 1 | 1 | 0 | 1 | ||
| 4 | 0 | 0 | 0 | 1 | ||
| 5 | 1 | 0 | 0 | 1 | ||
| ... | ... | ... | ... | ... | ... | ... |
| B_ビジネス | 10_福岡 | 9996 | 1 | 1 | 0 | 1 |
| 9997 | 1 | 1 | 0 | 1 | ||
| 9998 | 1 | 0 | 1 | 0 | ||
| 9999 | 0 | 1 | 0 | 1 | ||
| 10000 | 1 | 1 | 0 | 0 |
9879 rows × 4 columns
# 「カテゴリー」のクロス集計と「エリア」のクロス集計を連結する
aggregate_ps_df = pd.concat(
[
cross_1000_ps_df.groupby(level='カテゴリー').sum(),
cross_1000_ps_df.groupby(level='エリア').sum()
]
)
# DataFrame を表示する
print(aggregate_ps_df.shape)
display(aggregate_ps_df)
(12, 4)
| 表層 | ポジ | ネガ | 総合1-2 | 総合4-5 |
|---|---|---|---|---|
| A_レジャー | 3715 | 2212 | 299 | 4218 |
| B_ビジネス | 3036 | 1921 | 276 | 4105 |
| 01_登別 | 720 | 486 | 81 | 798 |
| 02_草津 | 756 | 464 | 64 | 830 |
| 03_箱根 | 772 | 489 | 72 | 839 |
| 04_道後 | 670 | 369 | 49 | 844 |
| 05_湯布院 | 797 | 404 | 33 | 907 |
| 06_札幌 | 633 | 388 | 60 | 826 |
| 07_名古屋 | 590 | 375 | 55 | 822 |
| 08_東京 | 601 | 372 | 63 | 811 |
| 09_大阪 | 582 | 389 | 52 | 833 |
| 10_福岡 | 630 | 397 | 46 | 813 |
# 必要ライブラリのインポート
import mca
# ライブラリ mca による対応分析
ncols = aggregate_ps_df.shape[1]
mca_ben = mca.MCA(aggregate_ps_df, ncols=ncols, benzecri=False)
# 行方向および列方向の値を取り出す
row_coord = mca_ben.fs_r(N=2)
col_coord = mca_ben.fs_c(N=2)
# 固有値を求める
eigenvalues = mca_ben.L
total = np.sum(eigenvalues)
# 寄与率を求める
explained_inertia = 100 * eigenvalues / total
# 行方向および列方向のラベルを取得する
row_labels = aggregate_ps_df.index
col_labels = aggregate_ps_df.columns
# プロットする
plot_coresp(row_coord, col_coord, row_labels, col_labels, explained_inertia)
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline
# コーディングルール
coding_or = coding_pos
# サブルーチン
def sort_and_plot(name, group):
# 「カテゴリー」ごとに Jaccard 係数でソートする
sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]
# Jaccard 係数Top 75語をソートして抽出する
group_cross_df = group.iloc[:,sorted_columns]
# 共起行列を作成する
X = group_cross_df.values
X = csc_matrix(X)
Xc = (X.T * X)
Xc = np.triu(Xc.toarray())
# コーディングルールで絞り込む
index = docs_df['表層'].str.contains("|".join(coding_or))
group_cross_df = group_cross_df[group_cross_df.index.get_level_values('文書ID').isin(docs_df.loc[index, '文書ID'])]
# 共起行列を DataFrame に整える
group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)
# 共起行列の中身を Jaccard 係数に入れ替える
group_jaccard_df = jaccard_coef(group_cooccur_df, group_cross_df)
# 抽出語の出現回数を取得する
word_counts = group_cross_df.sum(axis=0).values
# プロットする
ax = fig.add_subplot(4, 3, i+1)
plot_cooccur_network_with_code_ax(ax, group_jaccard_df, word_counts, np.sort(group_jaccard_df.values.reshape(-1))[::-1][120], coding_or)
ax.set_title(name)
# プロットの準備
fig = plt.figure(figsize=(20, 28))
i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
# サブルーチンを呼ぶ
sort_and_plot(name, group)
i += 1
# エリアごとのループ
for sub_name, sub_group in group.groupby(level='エリア'):
# サブルーチンを呼ぶ
sort_and_plot(sub_name, sub_group)
i += 1
# プロットの仕上げ
plt.tight_layout()
plt.show()
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline
# コーディングルール
coding_or = coding_neg
# サブルーチン
def sort_and_plot(name, group):
# 「カテゴリー」ごとに Jaccard 係数でソートする
sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]
# Jaccard 係数Top 75語をソートして抽出する
group_cross_df = group.iloc[:,sorted_columns]
# 共起行列を作成する
X = group_cross_df.values
X = csc_matrix(X)
Xc = (X.T * X)
Xc = np.triu(Xc.toarray())
# コーディングルールで絞り込む
index = docs_df['表層'].str.contains("|".join(coding_or))
group_cross_df = group_cross_df[group_cross_df.index.get_level_values('文書ID').isin(docs_df.loc[index, '文書ID'])]
# 共起行列を DataFrame に整える
group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)
# 共起行列の中身を Jaccard 係数に入れ替える
group_jaccard_df = jaccard_coef(group_cooccur_df, group_cross_df)
# 抽出語の出現回数を取得する
word_counts = group_cross_df.sum(axis=0).values
# プロットする
ax = fig.add_subplot(4, 3, i+1)
plot_cooccur_network_with_code_ax(ax, group_jaccard_df, word_counts, np.sort(group_jaccard_df.values.reshape(-1))[::-1][120], coding_or)
ax.set_title(name)
# プロットの準備
fig = plt.figure(figsize=(20, 28))
i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
# サブルーチンを呼ぶ
sort_and_plot(name, group)
i += 1
# エリアごとのループ
for sub_name, sub_group in group.groupby(level='エリア'):
# サブルーチンを呼ぶ
sort_and_plot(sub_name, sub_group)
i += 1
# プロットの仕上げ
plt.tight_layout()
plt.show()
「登別」と「道後」で「すばらしい」という単語が含まれている口コミを表示する
# 検索条件
search_index = \
all_df['エリア'].isin(['01_登別', '05_道後']) & \
(all_df['コメント'].str.contains('素晴らしい') | all_df['コメント'].str.contains('すばらしい'))
# 検索する
result_df = all_df[search_index]
# DataFrame を表示する
print(result_df.shape)
display(result_df.head())
# CSV に保存する
result_df.to_csv("output-1.csv", header=True)
(52, 18)
| カテゴリー | エリア | 施設番号 | 施設名 | コメント | 総合 | サービス | 立地 | 部屋 | 設備・アメニティ | 風呂 | 食事 | 旅行の目的 | 同伴者 | 宿泊年月 | 投稿者 | 年代 | 性別 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 4 | A_レジャー | 01_登別 | 80732 | 登別カルルス温泉 湯元オロフレ荘 | カルルス温泉郷の静かな立地、そして湯元の素晴らしい泉質、美味しいお料理、飾らない中にも親切な... | 5 | 5 | 5 | 5 | 5.0 | 5.0 | 5.0 | レジャー | 家族 | 44986 | 投稿者 | na | na |
| 75 | A_レジャー | 01_登別 | 14467 | 洞爺湖温泉 ホテルグランド トーヤ | スタッフさんの対応が大変良かったです。建物自体は古めだけど清掃が隅々までいき届いており、大変... | 5 | 5 | 5 | 4 | 4.0 | 4.0 | 5.0 | レジャー | 家族 | 44774 | 投稿者 | na | na |
| 81 | A_レジャー | 01_登別 | 139962 | ザ レイクビュー TOYA 乃の風リゾート | 娘の大学受験お疲れ様の家族旅行で、利用しました。展望露天風呂から、羊蹄山がバーンと見えて、圧... | 4 | 4 | 5 | 4 | 4.0 | 5.0 | 4.0 | レジャー | 家族 | 44958 | 投稿者 | na | na |
| 102 | A_レジャー | 01_登別 | 109022 | 心のリゾート 海の別邸ふる川 | 料理、風呂、部屋とても素晴らしい。 | 5 | 5 | 5 | 5 | 5.0 | 5.0 | 5.0 | レジャー | 一人 | 44743 | 投稿者 | na | na |
| 116 | A_レジャー | 01_登別 | 149046 | 緑の風リゾート きたゆざわ | 3度目の宿泊、いつもながら素晴らしいの一言。スタッフ、食事、部屋、温泉全て最高です。スタッフ... | 5 | 5 | 5 | 5 | 5.0 | 5.0 | 5.0 | レジャー | 家族 | 44743 | 投稿者 | na | na |
「東京」と「福岡」で「うるさい」という単語が含まれている口コミを表示する
# 検索条件
search_index = \
all_df['エリア'].isin(['08_東京', '10_福岡']) & \
all_df['コメント'].str.contains('うるさい')
# 検索する
result_df = all_df[search_index]
# DataFrame を表示する
print(result_df.shape)
display(result_df.head())
# CSV に保存する
result_df.to_csv("output-2.csv", header=True)
(8, 18)
| カテゴリー | エリア | 施設番号 | 施設名 | コメント | 総合 | サービス | 立地 | 部屋 | 設備・アメニティ | 風呂 | 食事 | 旅行の目的 | 同伴者 | 宿泊年月 | 投稿者 | 年代 | 性別 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 7071 | B_ビジネス | 08_東京 | 137984 | アゴーラプレイス 東京浅草 | 連泊プランなのにタオルetc.の補充分をフロントで受け取る際に何度も確認され、すごく時間が掛... | 3 | 2 | 4 | 3 | 2.0 | 2.0 | NaN | その他 | 家族 | 44805 | 投稿者 | na | na |
| 7180 | B_ビジネス | 08_東京 | 5587 | ホテルリンクス | Wi-Fiが全く繋がらなくて困りました。暖房の音がうるさいです。 | 3 | 3 | 3 | 3 | 3.0 | 3.0 | 3.0 | ビジネス | 一人 | 44927 | Tom Bradry | 60代 | 男性 |
| 7577 | B_ビジネス | 08_東京 | 1746 | the b 三軒茶屋(ザビー さんげんぢゃや) | 分かりづらい部屋もエレベーターもその他全般狭い建物もへ古いからかうるさい | 2 | 3 | 2 | 1 | 3.0 | 1.0 | NaN | ビジネス | 一人 | 44713 | うしおちゃん | 40代 | 女性 |
| 9169 | B_ビジネス | 10_福岡 | 17701 | 博多グリーンホテル天神 | 何度か宿泊しています。ちょっと設備が古くなってきた感はありますが、必要な物は揃っています。な... | 4 | 4 | 5 | 2 | 4.0 | NaN | NaN | レジャー | 一人 | 45047 | sadstar | 40代 | 女性 |
| 9592 | B_ビジネス | 10_福岡 | 167058 | 静鉄ホテルプレジオ博多駅前 | 電車の音が少しうるさいけど、快適に過ごせます。敷地内で、福岡市のレンタサイクルも借りれます。 | 5 | 4 | 4 | 4 | 5.0 | 5.0 | 5.0 | ビジネス | 一人 | 44562 | 投稿者 | na | na |
(1) 辞書追加前に MeCab の解析結果を確認する
import MeCab
tagger = MeCab.Tagger("-r ../tools/usr/local/etc/mecabrc")
print(tagger.parse("この泉質は極上です"))
print(tagger.parse("この海鮮丼は美味しいです"))
print(tagger.parse("近くにスカイツリーがあります"))
print(tagger.parse("浴室にバスタオルがありません"))
この 連体詞,*,*,*,*,*,この,コノ,コノ 泉 名詞,一般,*,*,*,*,泉,イズミ,イズミ 質 名詞,接尾,一般,*,*,*,質,シツ,シツ は 助詞,係助詞,*,*,*,*,は,ハ,ワ 極上 名詞,一般,*,*,*,*,極上,ゴクジョウ,ゴクジョー です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス EOS この 連体詞,*,*,*,*,*,この,コノ,コノ 海鮮 名詞,一般,*,*,*,*,海鮮,カイセン,カイセン 丼 名詞,一般,*,*,*,*,丼,ドンブリ,ドンブリ は 助詞,係助詞,*,*,*,*,は,ハ,ワ 美味しい 形容詞,自立,*,*,形容詞・イ段,基本形,美味しい,オイシイ,オイシイ です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス EOS 近く 名詞,副詞可能,*,*,*,*,近く,チカク,チカク に 助詞,格助詞,一般,*,*,*,に,ニ,ニ スカイ 名詞,一般,*,*,*,*,スカイ,スカイ,スカイ ツリー 名詞,一般,*,*,*,*,ツリー,ツリー,ツリー が 助詞,格助詞,一般,*,*,*,が,ガ,ガ あり 動詞,自立,*,*,五段・ラ行,連用形,ある,アリ,アリ ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス EOS 浴室 名詞,一般,*,*,*,*,浴室,ヨクシツ,ヨクシツ に 助詞,格助詞,一般,*,*,*,に,ニ,ニ バス 名詞,一般,*,*,*,*,バス,バス,バス タオル 名詞,一般,*,*,*,*,タオル,タオル,タオル が 助詞,格助詞,一般,*,*,*,が,ガ,ガ あり 動詞,自立,*,*,五段・ラ行,連用形,ある,アリ,アリ ませ 助動詞,*,*,*,特殊・マス,未然形,ます,マセ,マセ ん 助動詞,*,*,*,不変化型,基本形,ん,ン,ン EOS
(2) 辞書追加前に CaboCha の解析結果を確認する
import CaboCha
cp = CaboCha.Parser("-r ../tools/usr/local/etc/cabocharc")
print(cp.parse("この泉質は極上です").toString(CaboCha.FORMAT_LATTICE))
print(cp.parse("この海鮮丼は美味しいです").toString(CaboCha.FORMAT_LATTICE))
print(cp.parse("近くにスカイツリーがあります").toString(CaboCha.FORMAT_LATTICE))
print(cp.parse("浴室にバスタオルがありません").toString(CaboCha.FORMAT_LATTICE))
* 0 1D 0/0 1.423997 この 連体詞,*,*,*,*,*,この,コノ,コノ * 1 2D 1/2 1.423997 泉 名詞,一般,*,*,*,*,泉,イズミ,イズミ 質 名詞,接尾,一般,*,*,*,質,シツ,シツ は 助詞,係助詞,*,*,*,*,は,ハ,ワ * 2 -1D 0/1 0.000000 極上 名詞,一般,*,*,*,*,極上,ゴクジョウ,ゴクジョー です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス EOS * 0 1D 0/0 1.479074 この 連体詞,*,*,*,*,*,この,コノ,コノ * 1 2D 1/2 1.479074 海鮮 名詞,一般,*,*,*,*,海鮮,カイセン,カイセン 丼 名詞,一般,*,*,*,*,丼,ドンブリ,ドンブリ は 助詞,係助詞,*,*,*,*,は,ハ,ワ * 2 -1D 0/1 0.000000 美味しい 形容詞,自立,*,*,形容詞・イ段,基本形,美味しい,オイシイ,オイシイ です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス EOS * 0 2D 0/1 -2.105621 近く 名詞,副詞可能,*,*,*,*,近く,チカク,チカク に 助詞,格助詞,一般,*,*,*,に,ニ,ニ * 1 2D 1/2 -2.105621 スカイ 名詞,一般,*,*,*,*,スカイ,スカイ,スカイ ツリー 名詞,一般,*,*,*,*,ツリー,ツリー,ツリー が 助詞,格助詞,一般,*,*,*,が,ガ,ガ * 2 -1D 0/1 0.000000 あり 動詞,自立,*,*,五段・ラ行,連用形,ある,アリ,アリ ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス EOS * 0 2D 0/1 -1.725976 浴室 名詞,一般,*,*,*,*,浴室,ヨクシツ,ヨクシツ に 助詞,格助詞,一般,*,*,*,に,ニ,ニ * 1 2D 1/2 -1.725976 バス 名詞,一般,*,*,*,*,バス,バス,バス タオル 名詞,一般,*,*,*,*,タオル,タオル,タオル が 助詞,格助詞,一般,*,*,*,が,ガ,ガ * 2 -1D 0/2 0.000000 あり 動詞,自立,*,*,五段・ラ行,連用形,ある,アリ,アリ ませ 助動詞,*,*,*,特殊・マス,未然形,ます,マセ,マセ ん 助動詞,*,*,*,不変化型,基本形,ん,ン,ン EOS
(1) 追加したい形態素の情報を CSV ファイル(user_dic.csv)に追記する
!echo '"泉質",-1,-1,1,名詞,一般,*,*,*,*,泉質,センシツ,センシツ,USER"' >> ../tools/usr/local/lib/mecab/dic/ipadic/user_dic.csv
!echo '"海鮮丼",-1,-1,1,名詞,一般,*,*,*,*,海鮮丼,カイセンドン,カイセンドン,USER"' >> ../tools/usr/local/lib/mecab/dic/ipadic/user_dic.csv
!echo '"スカイツリー",-1,-1,1,名詞,一般,*,*,*,*,スカイツリー,スカイツリー,スカイツリー,USER"' >> ../tools/usr/local/lib/mecab/dic/ipadic/user_dic.csv
!echo '"バスタオル",-1,-1,1,名詞,一般,*,*,*,*,バスタオル,バスタオル,バスタオル,USER"' >> ../tools/usr/local/lib/mecab/dic/ipadic/user_dic.csv
!cat ../tools/usr/local/lib/mecab/dic/ipadic/user_dic.csv
"湯畑",-1,-1,1,名詞,一般,*,*,*,*,湯畑,ユバタケ,ユバタケ,USER" "泉質",-1,-1,1,名詞,一般,*,*,*,*,泉質,センシツ,センシツ,USER" "海鮮丼",-1,-1,1,名詞,一般,*,*,*,*,海鮮丼,カイセンドン,カイセンドン,USER" "スカイツリー",-1,-1,1,名詞,一般,*,*,*,*,スカイツリー,スカイツリー,スカイツリー,USER" "バスタオル",-1,-1,1,名詞,一般,*,*,*,*,バスタオル,バスタオル,バスタオル,USER"
(2) CSVファイル(user_dic.csv)をコンパイルして辞書(user.dic)を作成する
!../tools/usr/local/libexec/mecab/mecab-dict-index \
-d ../tools/usr/local/lib/mecab/dic/ipadic \
-u ../tools/usr/local/lib/mecab/dic/ipadic/user.dic \
-f utf-8 -t utf-8 \
../tools/usr/local/lib/mecab/dic/ipadic/user_dic.csv
reading ../tools/usr/local/lib/mecab/dic/ipadic/user_dic.csv ... 5 emitting double-array: 100% |###########################################| done!
(1) 辞書追加後に MeCab の解析結果を確認する
import MeCab
tagger = MeCab.Tagger("-r ../tools/usr/local/etc/mecabrc")
print(tagger.parse("この泉質は極上です"))
print(tagger.parse("この海鮮丼は美味しいです"))
print(tagger.parse("近くにスカイツリーがあります"))
print(tagger.parse("浴室にバスタオルがありません"))
この 連体詞,*,*,*,*,*,この,コノ,コノ 泉質 名詞,一般,*,*,*,*,泉質,センシツ,センシツ,USER" は 助詞,係助詞,*,*,*,*,は,ハ,ワ 極上 名詞,一般,*,*,*,*,極上,ゴクジョウ,ゴクジョー です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス EOS この 連体詞,*,*,*,*,*,この,コノ,コノ 海鮮丼 名詞,一般,*,*,*,*,海鮮丼,カイセンドン,カイセンドン,USER" は 助詞,係助詞,*,*,*,*,は,ハ,ワ 美味しい 形容詞,自立,*,*,形容詞・イ段,基本形,美味しい,オイシイ,オイシイ です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス EOS 近く 名詞,副詞可能,*,*,*,*,近く,チカク,チカク に 助詞,格助詞,一般,*,*,*,に,ニ,ニ スカイツリー 名詞,一般,*,*,*,*,スカイツリー,スカイツリー,スカイツリー,USER" が 助詞,格助詞,一般,*,*,*,が,ガ,ガ あり 動詞,自立,*,*,五段・ラ行,連用形,ある,アリ,アリ ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス EOS 浴室 名詞,一般,*,*,*,*,浴室,ヨクシツ,ヨクシツ に 助詞,格助詞,一般,*,*,*,に,ニ,ニ バスタオル 名詞,一般,*,*,*,*,バスタオル,バスタオル,バスタオル,USER" が 助詞,格助詞,一般,*,*,*,が,ガ,ガ あり 動詞,自立,*,*,五段・ラ行,連用形,ある,アリ,アリ ませ 助動詞,*,*,*,特殊・マス,未然形,ます,マセ,マセ ん 助動詞,*,*,*,不変化型,基本形,ん,ン,ン EOS
(2) 辞書追加後に CaboCha の解析結果を確認する
import CaboCha
cp = CaboCha.Parser("-r ../tools/usr/local/etc/cabocharc")
print(cp.parse("この泉質は極上です").toString(CaboCha.FORMAT_LATTICE))
print(cp.parse("この海鮮丼は美味しいです").toString(CaboCha.FORMAT_LATTICE))
print(cp.parse("近くにスカイツリーがあります").toString(CaboCha.FORMAT_LATTICE))
print(cp.parse("浴室にバスタオルがありません").toString(CaboCha.FORMAT_LATTICE))
* 0 1D 0/0 1.509856 この 連体詞,*,*,*,*,*,この,コノ,コノ * 1 2D 0/1 1.509856 泉質 名詞,一般,*,*,*,*,泉質,センシツ,センシツ,USER" は 助詞,係助詞,*,*,*,*,は,ハ,ワ * 2 -1D 0/1 0.000000 極上 名詞,一般,*,*,*,*,極上,ゴクジョウ,ゴクジョー です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス EOS * 0 1D 0/0 1.479074 この 連体詞,*,*,*,*,*,この,コノ,コノ * 1 2D 0/1 1.479074 海鮮丼 名詞,一般,*,*,*,*,海鮮丼,カイセンドン,カイセンドン,USER" は 助詞,係助詞,*,*,*,*,は,ハ,ワ * 2 -1D 0/1 0.000000 美味しい 形容詞,自立,*,*,形容詞・イ段,基本形,美味しい,オイシイ,オイシイ です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス EOS * 0 2D 0/1 -2.105621 近く 名詞,副詞可能,*,*,*,*,近く,チカク,チカク に 助詞,格助詞,一般,*,*,*,に,ニ,ニ * 1 2D 0/1 -2.105621 スカイツリー 名詞,一般,*,*,*,*,スカイツリー,スカイツリー,スカイツリー,USER" が 助詞,格助詞,一般,*,*,*,が,ガ,ガ * 2 -1D 0/1 0.000000 あり 動詞,自立,*,*,五段・ラ行,連用形,ある,アリ,アリ ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス EOS * 0 2D 0/1 -1.728522 浴室 名詞,一般,*,*,*,*,浴室,ヨクシツ,ヨクシツ に 助詞,格助詞,一般,*,*,*,に,ニ,ニ * 1 2D 0/1 -1.728522 バスタオル 名詞,一般,*,*,*,*,バスタオル,バスタオル,バスタオル,USER" が 助詞,格助詞,一般,*,*,*,が,ガ,ガ * 2 -1D 0/2 0.000000 あり 動詞,自立,*,*,五段・ラ行,連用形,ある,アリ,アリ ませ 助動詞,*,*,*,特殊・マス,未然形,ます,マセ,マセ ん 助動詞,*,*,*,不変化型,基本形,ん,ン,ン EOS